namespace HLSSignature
{
	using System;
	using Extensibility;
	using System.Runtime.InteropServices;
    using System.Drawing;
    using System.IO;
    using System.Reflection;
    using System.Security.Cryptography;
    using System.Security.Cryptography.X509Certificates;
    using System.Security.Cryptography.Xml;
    using System.Text;
    using System.Windows.Forms;
    using System.Xml;

    using Microsoft.Office.Core;
    using stdole;
    using HLSSignature.Utils;

    

    using XmlDataObject = System.Security.Cryptography.Xml.DataObject;

	#region Read me for Add-in installation and setup information.
	// When run, the Add-in wizard prepared the registry for the Add-in.
	// At a later time, if the Add-in becomes unavailable for reasons such as:
	//   1) You moved this project to a computer other than which is was originally created on.
	//   2) You chose 'Yes' when presented with a message asking if you wish to remove the Add-in.
	//   3) Registry corruption.
	// you will need to re-register the Add-in by building the HLSSignatureSetup project, 
	// right click the project in the Solution Explorer, then choose install.
	#endregion
	
	/// <summary>
	///   The object for implementing an Add-in.
	/// </summary>
	/// <seealso class='IDTExtensibility2' />
	[GuidAttribute("7C6FB3C2-4BAC-4C15-813E-9DF6AE03F4FA"), ProgId("HLSSignature.Connect")]
	public class Connect : Object, Extensibility.IDTExtensibility2,SignatureProvider
	{
		/// <summary>
		///		Implements the constructor for the Add-in object.
		///		Place your initialization code within this method.
		/// </summary>
		public Connect()
		{
		}

		/// <summary>
		///      Implements the OnConnection method of the IDTExtensibility2 interface.
		///      Receives notification that the Add-in is being loaded.
		/// </summary>
		/// <param term='application'>
		///      Root object of the host application.
		/// </param>
		/// <param term='connectMode'>
		///      Describes how the Add-in is being loaded.
		/// </param>
		/// <param term='addInInst'>
		///      Object representing this Add-in.
		/// </param>
		/// <seealso class='IDTExtensibility2' />
		public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom)
		{
			applicationObject = application;
			addInInstance = addInInst;
		}

		/// <summary>
		///     Implements the OnDisconnection method of the IDTExtensibility2 interface.
		///     Receives notification that the Add-in is being unloaded.
		/// </summary>
		/// <param term='disconnectMode'>
		///      Describes how the Add-in is being unloaded.
		/// </param>
		/// <param term='custom'>
		///      Array of parameters that are host application specific.
		/// </param>
		/// <seealso class='IDTExtensibility2' />
		public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom)
		{
		}

		/// <summary>
		///      Implements the OnAddInsUpdate method of the IDTExtensibility2 interface.
		///      Receives notification that the collection of Add-ins has changed.
		/// </summary>
		/// <param term='custom'>
		///      Array of parameters that are host application specific.
		/// </param>
		/// <seealso class='IDTExtensibility2' />
		public void OnAddInsUpdate(ref System.Array custom)
		{
		}

		/// <summary>
		///      Implements the OnStartupComplete method of the IDTExtensibility2 interface.
		///      Receives notification that the host application has completed loading.
		/// </summary>
		/// <param term='custom'>
		///      Array of parameters that are host application specific.
		/// </param>
		/// <seealso class='IDTExtensibility2' />
		public void OnStartupComplete(ref System.Array custom)
		{
		}

		/// <summary>
		///      Implements the OnBeginShutdown method of the IDTExtensibility2 interface.
		///      Receives notification that the host application is being unloaded.
		/// </summary>
		/// <param term='custom'>
		///      Array of parameters that are host application specific.
		/// </param>
		/// <seealso class='IDTExtensibility2' />
		public void OnBeginShutdown(ref System.Array custom)
		{
		}
		
		private object applicationObject;
		private object addInInstance;

        #region SignatureProvider Members

        IPictureDisp SignatureProvider.GenerateSignatureLineImage(SignatureLineImage siglnimg, SignatureSetup sigsetup, SignatureInfo siginfo, object xmldsigStream)
        {
            int signAreaWidth = 400;
            int signAreaHeight = 200;
            IPictureDisp sigline = null;
            SAFEImage safe = new SAFEImage();

            System.Drawing.Bitmap draw = null;

            if (siglnimg == SignatureLineImage.siglnimgUnsigned)
            {
                draw = safe.CreateSignLine("Requested Signature:", sigsetup.SuggestedSigner, signAreaWidth, signAreaHeight);
            }
            else if (siglnimg == SignatureLineImage.siglnimgSignedValid)
            {
                draw = safe.CreateSAFEImage(SAFEImage.enmSAFESignatureStatus.ValidateSuccess,
                    (string)siginfo.GetCertificateDetail(Microsoft.Office.Core.CertificateDetail.certdetSubject),
                    SAFEImage.SAFEDisclaimer + "\r\n" + siginfo.SignatureComment,
                     //DateTime.Now,
                    (DateTime) siginfo.GetSignatureDetail(SignatureDetail.sigdetLocalSigningTime),
                    signAreaWidth,
                    signAreaHeight);
            }
            else if (siglnimg == SignatureLineImage.siglnimgSignedInvalid)
            {
                draw = safe.CreateSAFEImage(SAFEImage.enmSAFESignatureStatus.ValidateFail,
                    (string)siginfo.GetCertificateDetail(Microsoft.Office.Core.CertificateDetail.certdetSubject),
                    SAFEImage.SAFEDisclaimer + "\r\n" + siginfo.SignatureComment,
                    (DateTime)siginfo.GetSignatureDetail(SignatureDetail.sigdetLocalSigningTime),
                    signAreaWidth,
                    signAreaHeight);

            }
            else if (siglnimg == SignatureLineImage.siglnimgSoftwareRequired)
            {
                draw = safe.CreateSignLineSoftwareRequired("Software Required",
                    signAreaWidth,
                    signAreaHeight);
                //g.FillRectangle(new SolidBrush(Color.LightSlateGray), 2, 2, 196, 96);
                //g.DrawString("Software Required", new System.Drawing.Font("Verdana", 10), new SolidBrush(Color.AliceBlue), new PointF(20, 20));
            }
            //else if (siglnimg == SignatureLineImage.siglnimgSigned)
            //{ }
            else
            {
                throw new NotImplementedException();
            }

            System.IntPtr hbitmap = draw.GetHbitmap(Color.Green);
            Image img = Image.FromHbitmap(hbitmap);

            sigline = (IPictureDisp)AxHost2.GetIPictureDispFromPicture(img);

            return sigline;

        }


        object SignatureProvider.GetProviderDetail(SignatureProviderDetail sigProvDetail)
        {
            switch (sigProvDetail)
            {
                case Microsoft.Office.Core.SignatureProviderDetail.sigprovdetHashAlgorithm:
                    return this.HashAlgorithmIdentifier;

                case Microsoft.Office.Core.SignatureProviderDetail.sigprovdetUIOnly:
                    return false;

                case Microsoft.Office.Core.SignatureProviderDetail.sigprovdetUrl:
                    return this.ProviderUrl;

                default:
                    return null;
            }
        }

        Array SignatureProvider.HashStream(object queryContinue, object stream)
        {
            using (COMStream comstream = new COMStream(stream))
            {
                using (HashAlgorithm hashalg = HashAlgorithm.Create(this.HashAlgorithmName))
                {
                    return hashalg.ComputeHash(comstream);
                }
            }
        }
        void SignatureProvider.NotifySignatureAdded(object parentWindow, SignatureSetup sigsetup, SignatureInfo siginfo)
        {
            using (Win32WindowFromOleWindow window = new Win32WindowFromOleWindow(parentWindow))
            {
                MessageBox.Show(window, "Signature has been applied", "Signing Ceremony", MessageBoxButtons.OK);
            }
        }
        void SignatureProvider.ShowSignatureDetails(object parentWindow, SignatureSetup sigsetup, SignatureInfo siginfo, object xmldsigStream, ref ContentVerificationResults contverresults, ref CertificateVerificationResults certverresults)
        {
            using (Win32WindowFromOleWindow window = new Win32WindowFromOleWindow(parentWindow))
            {
                using (SigningCeremonyForm signForm = new SigningCeremonyForm(sigsetup, siginfo))
                {
                    signForm.ShowDialog(window);
                }
            }
        }

        void SignatureProvider.ShowSignatureSetup(object parentWindow, SignatureSetup sigsetup)
        {
            bool firstInit = string.IsNullOrEmpty(sigsetup.AdditionalXml);
            if (sigsetup != null && !sigsetup.ReadOnly && firstInit)
            {
                sigsetup.SuggestedSigner = "Enter Signer Name";
                sigsetup.SigningInstructions = "Please sign this document.";
                sigsetup.ShowSignDate = true;
                sigsetup.AdditionalXml = "<HLSSignatureData />";
            }

            using (Win32WindowFromOleWindow window = new Win32WindowFromOleWindow(parentWindow))
            {
                using (SignatureSetupForm sigsetupForm = new SignatureSetupForm(sigsetup))
                {
                    sigsetupForm.ShowDialog(window);
                    if (!sigsetupForm.success && firstInit)
                        throw new System.Runtime.InteropServices.COMException("Cancelled", -2147467260 /*E_ABORT*/);
                }
            }
        }

        void SignatureProvider.ShowSigningCeremony(object parentWindow, SignatureSetup sigsetup, SignatureInfo siginfo)
        {
            using (Win32WindowFromOleWindow window = new Win32WindowFromOleWindow(parentWindow))
            {
                if (!((bool)siginfo.GetCertificateDetail(CertificateDetail.certdetAvailable)))
                {
                    MessageBox.Show(window, "You need a digital certificate to sign this document", "Signing Ceremony", MessageBoxButtons.OK);
                    throw new System.Runtime.InteropServices.COMException("Cancelled", -2147467260 /*E_ABORT*/);
                }

                using (SigningCeremonyForm signForm = new SigningCeremonyForm(sigsetup, siginfo))
                {
                    signForm.ShowDialog(window);
                    if (!signForm.success)
                        throw new System.Runtime.InteropServices.COMException("Cancelled", -2147467260 /*E_ABORT*/);
                }
            }
        }


        /// <sumary>
        /// Creates the hash to pass to the timestamp server in the RFC3161 request
        /// </sumary>
        /// <param name="bSignature">The XML signature being timestamped</param>
        /// <param name="ocspResponses">The OCSP responses for the signature certificate chain</param>
        /// <returns>The byte array to pass to the timestamp server</returns>
        byte[] GetTimestampHash(System.Collections.Generic.List<string> ocspResponses, byte[] bSignature)
        {
            // TODO:  make hash algorithm configurable
            Safe.HashEngine md = new Safe.HashEngine(Safe.SupportedHashAlgorithm.SHA1);

            // write the OCSP responses
            foreach (string ocsp in ocspResponses)
            {
                    byte[] binOcsp = Convert.FromBase64String(ocsp);
                    md.Update(binOcsp);
            }
            // write the signature bytes
            md.Update(bSignature);
            return md.Hash();
        }

        
        /// <summary>
        /// Signs the XML digital signature with the chosen certificate
        /// </summary>
        /// <param name="queryContinue">Provides a way to query the host application for permission to continue the verification operation.</param>
        /// <param name="sigsetup">Specifies configuration information about a signature line.</param>
        /// <param name="siginfo">Specifies information captured from the signing ceremony.</param>
        /// <param name="xmldsigStream">	Represents a steam of data containing XML, which represents an XMLDSIG object.</param>
        void SignatureProvider.SignXmlDsig(object queryContinue, SignatureSetup sigsetup, SignatureInfo siginfo, object xmldsigStream)
        {
            using (COMStream comstream = new COMStream(xmldsigStream))
            {
                XmlDocument xmldsig = new XmlDocument();
                xmldsig.PreserveWhitespace = true;
                xmldsig.Load(comstream);

                XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmldsig.NameTable);
                nsmgr.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#");

                XmlElement signature = xmldsig.DocumentElement;
                SignedXml signedXml = new SignedXml();
                signedXml.LoadXml(signature);

                // Cert
                X509Certificate2 cert = Connect.GetSigningCertificate(siginfo);

                // Check cert validity and get OCSP response
                System.Collections.Generic.List<String> ocspBase64 = null;
                try
                {
                    ocspBase64 = Safe.CertificateValidation.GetOcspResponses(
                        cert, 
                        new System.Collections.Generic.List<string>(PluginConfig.AcceptablePolicies),
                        (int)PluginConfig.ClockSkew);
                }
                catch (Safe.ChainBuildException chainExc)
                {
                    // Handle a problem building certificate chain for the signing cert.
                    // There will be more information in the exception message if the build
                    // failed with a recognizable error
                    MessageBox.Show(
                        chainExc.Message,
                        "Certificate Error",
                        MessageBoxButtons.OK
                    );
                    throw chainExc;

                }
                catch (Safe.OcspException ocspExc)
                {
                    // Handle the case where OCSP responses could not be obtained
                    // for one or more certificates in the chain
                    MessageBox.Show(
                        "OCSP responses could not be obtained for all elements in the chain",
                        "OCSP Error",
                        MessageBoxButtons.OK
                    );
                    throw ocspExc;
                }
                catch (Exception exc)
                {
                    MessageBox.Show("Unhandled Exception Type: " + exc.GetType() + "\r\n" + exc.Message + "\r\n" + exc.StackTrace);
                    throw exc;
                }

                KeyInfo keyInfo = new KeyInfo();
                if (cert.PrivateKey is RSA)
                {
                    keyInfo.AddClause(new RSAKeyValue((RSA)cert.PrivateKey));
                }
                else if (cert.PrivateKey is DSA)
                {
                    keyInfo.AddClause(new DSAKeyValue((DSA)cert.PrivateKey));
                }
                keyInfo.AddClause(new KeyInfoX509Data(cert));
                signedXml.SigningKey = cert.PrivateKey;
                signedXml.KeyInfo = keyInfo;

                // Compute signature
                signedXml.ComputeSignature();

                // Copy data from signed signature
                string[] xpathsToCopy = new string[]
                {
                    "./ds:SignedInfo",
                    "./ds:SignatureValue",
                    "./ds:KeyInfo",
                };

                XmlElement signedSignature = signedXml.GetXml();
                foreach (string xpathToCopy in xpathsToCopy)
                {
                    signature.ReplaceChild(
                        xmldsig.ImportNode(signedSignature.SelectSingleNode(xpathToCopy, nsmgr), true),
                        signature.SelectSingleNode(xpathToCopy, nsmgr));
                }

                // add signer's cert to the xml
                XmlNode certNode = xmldsig.CreateNode(XmlNodeType.Element, "SignerCert", "http://www.w3.org/2000/09/xmldsig#");

                // add OCSP responses to the xml 
                foreach (string ocspB64 in ocspBase64)
                {
                    XmlNode ocspNode = xmldsig.CreateNode(XmlNodeType.Element, "OCSP", "http://www.w3.org/2000/09/xmldsig#");
                    ocspNode.InnerText = ocspB64;
                    signature.AppendChild(ocspNode);
                }


                byte[] signatureValue = signedXml.SignatureValue;

                #region RFC3161 Timestamping
                /// Only apply a timestamp if the timestamp URL is configured in the application's 
                /// registry settings.  If not, no timestamp is applied.  This means that the signature
                /// validation will always take place in real-time.
                if (PluginConfig.TimestampUrl != null)
                {
                    byte[] tsData = GetTimestampHash(ocspBase64, signatureValue);
                    Safe.ManagedCryptBlob tsBlob = new Safe.ManagedCryptBlob(tsData);
                    Safe.TimestampRequest req = null;

                    // TODO: make hash alg configurable
                    String sha1 = "1.3.14.3.2.26";
                    int rc = 0;
                    try
                    {
                        req = new Safe.TimestampRequest(tsBlob, sha1);
                    }
                    catch (Safe.AsnException tsException)
                    {
                        rc = tsException.ErrorCode;
                    }
                    if (rc != 0)
                    {
                        MessageBox.Show(
                            "A trusted timestamp could not be retrieved",
                            "Timestamp Error",
                            MessageBoxButtons.OK
                        );
                        throw new Safe.TimestampException();
                    }
                    else
                    {
                        Safe.ManagedCryptBlob hashBlob = req.Hash;
                        Safe.ManagedCryptBlob tsRequest = req.Request;
                        Safe.Http.HttpTimestampRequest HttpTimestampRequest =
                            new Safe.Http.HttpTimestampRequest(tsRequest.Data);
                        try
                        {
                            byte[] tsResponse = HttpTimestampRequest.Submit(PluginConfig.TimestampUrl);
                            // add the timestamp response to the document
                            try
                            {
                                // TODO:  make the hash alg configurable
                                Safe.TimestampResponse objResponse = new Safe.TimestampResponse(tsResponse, sha1, hashBlob);
                                XmlNode tsNode = xmldsig.CreateNode(XmlNodeType.Element, "TS", "http://www.w3.org/2000/09/xmldsig#");
                                tsNode.InnerText = Convert.ToBase64String(tsResponse);
                                signature.AppendChild(tsNode);
                                tsNode = xmldsig.CreateNode(XmlNodeType.Element, "TS_HASHALG", "http://www.w3.org/2000/09/xmldsig#");
                                tsNode.InnerText = sha1;
                                signature.AppendChild(tsNode);
                            }
                            catch (Safe.AsnException asnExc)
                            {

                                // Handle encoding problem with the timestamp response
                                MessageBox.Show(
                                    "The timestamp server returned an invalid or unusable timestamp response",
                                    "Timestamp Error",
                                    MessageBoxButtons.OK
                                );
                                throw asnExc;
                            }
                            catch (Safe.ChainBuildException chainExc)
                            {
                                // Handle error validating the timestamp server cert
                                MessageBox.Show(
                                    "The timestamp server's certificate could not be validated",
                                    "Certificate Error",
                                    MessageBoxButtons.OK
                                );
                                throw chainExc;
                            }
                            catch (Safe.TimestampException tsExc)
                            {
                                // Handle error in the timestamp
                                MessageBox.Show(
                                    "The timestamp server returned an invalid or unusable timestamp response",
                                    "Timestamp Error",
                                    MessageBoxButtons.OK
                                );
                                throw tsExc;
                            }
                        }
                        catch (Exception exc)
                        {
                            // Handle http request failed
                            MessageBox.Show(
                                    "The timestamp request failed",
                                    "Timestamp Error",
                                    MessageBoxButtons.OK
                                );
                            throw exc;
                        }
                    }
                }
                #endregion


                // Save signature back to stream
                comstream.SetLength(0);
                comstream.Position = 0;
                xmldsig.Save(new XmlTextWriter(comstream, new UTF8Encoding(false)));
            }
        }



        /// <summary>
        /// Verify an XML signature and the certificate that created the signature.
        /// 1.  Validate the signature hash to make sure the document was not modified
        /// 2.  Retrieve the timestamp from the document and validate it.  If the timestamp
        /// is valid, the certificate is validated using the timestamp time.  If not, the cert
        /// is validated using the current time
        /// 3.  Retrieve the OCSP responses from the document.  
        /// 4.  Build and validate a certificate chain for the signer certificate, using the
        /// embedded OCSP responses to perform the revocation checking
        /// </summary>
        /// <param name="queryContinue">Provides a way to query the host application for permission to continue the verification operation.</param>
        /// <param name="sigsetup">Specifies configuration information about a signature line.</param>
        /// <param name="siginfo">Specifies information captured from the signing ceremony.</param>
        /// <param name="xmldsigStream">IStream</param>
        /// <param name="contverresults">Specifies the status of the signature verification action.</param>
        /// <param name="certverresults">Specifies the status of the signing certificate verification.</param>
        void SignatureProvider.VerifyXmlDsig(object queryContinue, SignatureSetup sigsetup, SignatureInfo siginfo, object xmldsigStream, ref ContentVerificationResults contverresults, ref CertificateVerificationResults certverresults)
        {
            try
            {
                using (COMStream comstream = new COMStream(xmldsigStream))
                {
                    /// set the status of the signature and the certificate to "unverified"
                    contverresults = ContentVerificationResults.contverresUnverified;
                    certverresults = CertificateVerificationResults.certverresUnverified;

                    XmlDocument xmldsig = new XmlDocument();
                    xmldsig.PreserveWhitespace = true;
                    xmldsig.Load(comstream);

                    XmlElement signature = xmldsig.DocumentElement;
                    SignedXml signedXml = new SignedXml();
                    signedXml.LoadXml(signature);

                    // get the signer's certificate from the signature
                    X509Certificate2 signerCert = null;
                    foreach (KeyInfoClause clause in signedXml.KeyInfo)
                    {
                        if (clause is System.Security.Cryptography.Xml.KeyInfoX509Data)
                        {
                            KeyInfoX509Data x509 = clause as System.Security.Cryptography.Xml.KeyInfoX509Data;
                            if (x509.Certificates.Count > 0)
                            {
                                signerCert = (X509Certificate2)x509.Certificates[0];
                            }
                        }
                    }
                    if (signerCert == null)
                    {
                        // Handle signer cert not being embedded in the XML
                        MessageBox.Show(
                                    "The signer's certificate could not be found",
                                    "Signature Error",
                                    MessageBoxButtons.OK
                                );
                        contverresults = ContentVerificationResults.contverresError;
                        throw new Exception("Signer certificate not embedded in XML signature");
                    }

                    // Validate that signature structure without checking the certificate
                    if (!signedXml.CheckSignature(signerCert, true))
                    {
                        /// Something was modified and the signature is no longer valid
                        /// Since the signature is invalid, there's no poing in verifying
                        /// the signer's certificate
                        contverresults = ContentVerificationResults.contverresModified;
                        return;
                    }
                    else
                    {
                        contverresults = ContentVerificationResults.contverresValid;
                    }


                    //get the embedded OCSP responses
                    XmlNamespaceManager nsmgr = new XmlNamespaceManager(xmldsig.NameTable);
                    nsmgr.AddNamespace("ds", "http://www.w3.org/2000/09/xmldsig#");
                    XmlNodeList ocspNodeList = signature.SelectNodes("./ds:OCSP", nsmgr);
                    System.Collections.Generic.List<string> ocspBase64 = new System.Collections.Generic.List<string>();
                    foreach (XmlNode ocspNode in ocspNodeList)
                    {
                        ocspBase64.Add(ocspNode.InnerText);
                    }

                    // get the signature binary
                    byte[] binSignature = signedXml.SignatureValue;

                    // Get the timestamp value
                    XmlNode tsNode = signature.SelectSingleNode("./ds:TS", nsmgr);
                    XmlNode tsHashAlg = signature.SelectSingleNode("./ds:TS_HASHALG", nsmgr);


                    /// If there is a timestamp embedded in the document, then it is parsed and validated.
                    /// If the timestamp is invalid (timestamp signature is invalid, or the timestamp 
                    /// certificate is expired,revoked,or untrusted), then the document signature is validated
                    /// in real-time.
                    Safe.TimestampResponse tsResp = null;
                    if (tsNode != null && tsHashAlg != null)
                    {
                        // Re-create the hash submitted to the timestamp server
                        byte[] tsHash = GetTimestampHash(ocspBase64, binSignature);
                        Safe.ManagedCryptBlob hashBlob = new Safe.ManagedCryptBlob(tsHash);

                            
                        try
                        {
                            // Parse the timestamp 
                            tsResp = new Safe.TimestampResponse(
                                Convert.FromBase64String(tsNode.InnerText),
                                tsHashAlg.InnerText,
                                hashBlob);
                        }
                        catch (Safe.AsnException)
                        {
                            // Handle encoding problem
                            MessageBox.Show(
                                        "The embedded timestamp response could not be decoded.  Validing signature in real-time.",
                                        "Timestamp Error",
                                        MessageBoxButtons.OK
                                    );
                        }
                        catch (Safe.ChainBuildException)
                        {
                            //Handle invalid or untrusted timestamp cert
                            MessageBox.Show(
                                        "The timestamp server's certificate is invalid, untrusted, or revoked.  Validing signature in real-time.",
                                        "Timestamp Error",
                                        MessageBoxButtons.OK
                                    );
                        }
                        catch (Safe.TimestampException)
                        {
                            // Handle bad timestamp value
                            MessageBox.Show(
                                        "The embedded timestamp is invalid.  Validing signature in real-time.",
                                        "Timestamp Error",
                                        MessageBoxButtons.OK
                                    );
                        }
                        catch (Exception unexpected)
                        {
                            MessageBox.Show("Unexpected error getting timestamp: " + unexpected.Message + ".  Validing signature in real-time.");
                        }
                    }


                    /// set the certificate verification status to "verifying"
                    certverresults = CertificateVerificationResults.certverresVerifying;


                    Safe.PathResult customPathCheckValue;
                    bool customPathCheckUsed = false;
                    if (tsResp == null)
                    {
                        /// Try to validate the signature certificate using the custom method, passing
                        /// in the current time.  This will attempt to validate the signature using the
                        /// OCSP responses embedded in the document.
                        customPathCheckUsed = true;
                        customPathCheckValue =
                            Safe.CertificateValidation.ValidateCertificate(
                                signerCert,
                                ocspBase64,
                                new System.Collections.Generic.List<string>(PluginConfig.AcceptablePolicies),
                                DateTime.Now,
                                (int)PluginConfig.ClockSkew);
                    }
                    else
                    {
                        /// Try to validate the certificate using the timestamp time.  This will attempt to
                        /// validate the signature using the OCSP responses embedded in the document.
                        customPathCheckUsed = true;
                        customPathCheckValue =
                            Safe.CertificateValidation.ValidateCertificate(
                                    signerCert, 
                                    ocspBase64, 
                                    new System.Collections.Generic.List<string>(PluginConfig.AcceptablePolicies), 
                                    tsResp.TimestampTime,
                                    (int)PluginConfig.ClockSkew
                                    );
                    }

                    /// if the revocation status embedded in the document can't be used, then check the
                    /// cert using the default CAPI validation.
                    if (customPathCheckValue == Safe.PathResult.RevocationUnavailable)
                    {
                        /// reset the flag to show that the custom path build result was not used, because
                        /// the revocation status could not be determined using the information embedded in
                        /// the document.
                        customPathCheckUsed = false;
                        // validate the certificate using the current time
                        if (signerCert.Verify())
                        {
                            certverresults = CertificateVerificationResults.certverresValid;
                        }
                        else
                        {
                            certverresults = CertificateVerificationResults.certverresInvalid;
                        }
                    }


                    /// handle the results of the custom path building operation
                    if( customPathCheckUsed )
                    {
                        switch (customPathCheckValue)
                        {
                            case Safe.PathResult.Good:
                                certverresults = CertificateVerificationResults.certverresValid;
                                break;

                            case Safe.PathResult.CertFailedPolicyCheck:

                                // Handle certificate not being a valid SAFE certificate
                                MessageBox.Show(
                                    "The signer's certificate does not express a valid SAFE policy",
                                    "Certificate Error",
                                    MessageBoxButtons.OK
                                    );
                                certverresults = CertificateVerificationResults.certverresInvalid;
                                break;

                            case Safe.PathResult.CertUntrusted:
                                // Handle untrusted certificate
                                MessageBox.Show(
                                    "The signer's certificate is untrusted",
                                    "Certificate Error",
                                    MessageBoxButtons.OK
                                );
                                certverresults = CertificateVerificationResults.certverresUntrusted;
                                break;

                            case Safe.PathResult.CertRevoked:
                                // Handle revoked certificate
                                MessageBox.Show(
                                    "A certificate in the chain is revoked",
                                    "Certificate Error",
                                    MessageBoxButtons.OK);
                                certverresults = CertificateVerificationResults.certverresRevoked;
                                break;

                            default:

                                // Handle any other error 
                                MessageBox.Show(
                                    "The signature is invalid (" + customPathCheckValue.ToString() + ")",
                                    "Certificate Error",
                                    MessageBoxButtons.OK
                                );
                                certverresults = CertificateVerificationResults.certverresInvalid;
                                break;
                        }
                    }
                }
            }
            catch (Exception unhandled)
            {
                MessageBox.Show("Unexpected Exception caught:\r\n" + unhandled.Message + "\r\n" + unhandled.StackTrace);
                certverresults = CertificateVerificationResults.certverresUnverified;
            }
        }

        /// <summary>
        /// Retrieves the signer certificate from CAPI based on the SignatureInfo
        /// </summary>
        /// <param name="siginfo"></param>
        /// <returns></returns>
        protected static X509Certificate2 GetSigningCertificate(SignatureInfo siginfo)
        {
            X509Store certstore = new X509Store(StoreLocation.CurrentUser);
            certstore.Open(OpenFlags.ReadOnly);

            X509Certificate2Collection certs = certstore.Certificates.Find(
                X509FindType.FindByThumbprint,
                siginfo.GetCertificateDetail(Microsoft.Office.Core.CertificateDetail.certdetThumbprint),
                false);

            if (certs.Count == 1)
                return certs[0];
            else
                return null;
        }

        #endregion
        #region Properties

        public virtual string HashAlgorithmName { get { return "SHA1"; } }
        public virtual string HashAlgorithmIdentifier { get { return SignedXml.XmlDsigSHA1Url; } }
        public virtual string ProviderUrl { get { return "http://www.microsoft.com"; } }

        #endregion Properties

    }
}